////////////////////////////////////////////////////////////////////////////////////////////////////

#include "CPlaneControl.h"
#include "CUserInterface.h"
#include "CLevelEditor.h"
#include "DoUndoCommands.h"
#include "CEventReceiver.h"
#include "ISceneCollisionManager.h"
#include "CMoveCommand.h"
#include "CMultiCommand.h"
#include "ISceneManager.h"
#include "ISceneNode.h"
#include "ICameraSceneNode.h"
#include "SViewFrustum.h"
#include "IrrlichtDevice.h"
////////////////////////////////////////////////////////////////////////////////////////////////////

extern CGlobals* g;
extern CUserInterface* ui;
extern CLevelEditor* editor;

////////////////////////////////////////////////////////////////////////////////////////////////////

ICameraSceneNode* cloneActiveCamera ();

////////////////////////////////////////////////////////////////////////////////////////////////////

void drawThick2DLine (position2di p1, position2di p2, int thickness, SColor color)
{
	for (int i = 0; i < thickness; ++i)
	{
		position2d<s32> offX(i - thickness/2, 0);
		position2d<s32> offY(0, i - thickness/2);
		g->driver->draw2DLine(p1 + offX, p2 + offX, color);
		g->driver->draw2DLine(p1 + offY, p2 + offY, color);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void drawThick2DLine (vector2df p1, vector2df p2, int thickness, SColor color)
{
	drawThick2DLine(position2di(s32(p1.X), s32(p1.Y)), position2di(s32(p2.X), s32(p2.Y)), thickness, color);
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void drawThick2DArrow (vector2df p1, vector2df p2, int thickness, SColor color)
{
	const float head_width = 3.0f;
	const float head_height = 6.0f;

	vector2df dir = p2 - p1;
	if (dir.getLength() < 1e-6f) return;

	dir.normalize();
	vector2df perp(-dir.Y, dir.X);

	drawThick2DLine(p1, p2, thickness, color);
	drawThick2DLine(p2 - dir * head_height - perp * head_width, p2, thickness, color);
	drawThick2DLine(p2 - dir * head_height + perp * head_width, p2, thickness, color);
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CPlaneControl::CPlaneControl (array<ISceneNode*> nodes, vector3df axis1, vector3df axis2, SColor normalColor, SColor highlightColor)
{
	m_type = E_PLANE;
	m_nodes = nodes;
	m_axis1 = axis1;
	m_axis2 = axis2;
	m_normalColor = normalColor;
	m_highlightColor = highlightColor;
	m_activated = false;
	m_highlighted = false;
	m_camera = 0;
	m_centroid = vector3df();
	for (u32 i = 0; i < m_nodes.size(); ++i)
	{
		m_centroid += m_nodes[i]->getAbsolutePosition();
	}
	m_centroid /= (f32)m_nodes.size();
	m_nodesStartPos.set_used(m_nodes.size());
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CPlaneControl::~CPlaneControl ()
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPlaneControl::OnEvent (const SEvent& event)
{
	switch (event.EventType)
	{
	case EET_MOUSE_INPUT_EVENT:
		return onMouseEvent(event.MouseInput);
	}
	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool isOnPositiveSide (vector2df pt, vector2df line1, vector2df line2)
{
	vector2df d = line2 - line1;
	vector2df perp(-d.Y, d.X);
	return (pt - line1).dotProduct(perp) > 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPlaneControl::isPointOverControl (position2di pt) const
{
	array<vector2df> coords;
	getScreenCoordinates(&coords);

	if (coords.size() == 4)
	{
		vector2df ptf(f32(pt.X), f32(pt.Y));
		bool side1 = isOnPositiveSide(ptf, coords[0], coords[1]);
		bool side2 = isOnPositiveSide(ptf, coords[1], coords[2]);
		bool side3 = isOnPositiveSide(ptf, coords[2], coords[3]);
		bool side4 = isOnPositiveSide(ptf, coords[3], coords[0]);

		return side1 && side2 && side3 && side4 ||
			!side1 && !side2 && !side3 && !side4;
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool project3DTo2D (vector3df pt, vector2df* result, ICameraSceneNode* camera = 0)
{
	if (camera == 0)
		camera = g->sceneManager->getActiveCamera();
	rect<s32> viewport = g->driver->getViewPort();
	dimension2di dim = viewport.getSize() / 2;

	matrix4 trans = camera->getProjectionMatrix() * camera->getViewMatrix();
	f32 transformedPos[4] = { pt.X, pt.Y, pt.Z, 1.0f };
	trans.multiplyWith1x4Matrix(transformedPos);

	if (transformedPos[3] < 0.01) return false;

	result->X = dim.Width * (1.0f + transformedPos[0] / transformedPos[3]);
	result->Y = dim.Height * (1.0f - transformedPos[1] / transformedPos[3]);
	return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPlaneControl::onMouseEvent (const SEvent::SMouseInput& event)
{
	position2di mouse = g->device->getCursorControl()->getPosition();
	
	if (editor->isPointOverGUI(mouse))
		return false;

	bool mouseOverControl = isPointOverControl(mouse);

	m_highlighted = mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL);

	switch (event.Event)
	{
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL))
		{
			array<vector2df> coords;
			getScreenCoordinates(&coords);
			mouse.X = (s32)coords[0].X;
			mouse.Y = (s32)coords[0].Y;

			m_activated = true;
			g->eventReceiver->GrabAllEvents(this);

			for (u32 i = 0; i < m_nodes.size(); ++i)
				m_nodesStartPos[i] = m_nodes[i]->getPosition();

			m_mouseStartPos = mouse;

			m_camera = cloneActiveCamera();

			g->device->getCursorControl()->setPosition(mouse);

			return true;
		}
		break;

	case EMIE_LMOUSE_LEFT_UP:
		if (m_activated)
		{
			m_activated = false;
			g->eventReceiver->ReleaseGrab(this);
			m_camera->remove();
			m_camera = 0;
			if (m_nodes.size() > 1)
			{
				CMultiCommand* moves = new CMultiCommand;
				moves->setDescription(stringw(L"move ") + stringw(m_nodes.size()) + " objects");
				for (u32 i = 0; i < m_nodes.size(); ++i)
				{
					CMoveCommand* move = new CMoveCommand(m_nodes[i]->getID(), m_nodesStartPos[i], m_nodes[i]->getPosition());
					moves->addCommand(move);
					move->drop();
				}
				editor->AddCommand(moves);
			}
			else
			{
				vector3df newPos = m_nodes[0]->getPosition();
				editor->AddCommand(new CMoveCommand(m_nodes[0]->getID(), m_nodesStartPos[0], newPos));
				//ISceneNode* p = m_nodes[0]->getParent();
				//if ( p && (strcmp(p->getName(), "userdefinedmesh") == 0))
				//{
				//	editor->updateMeshFromNodes( p );
				//}
			}
			return true;
		}
		break;
	}

	if (m_activated)
	{
		vector3df mouseStart;
		plane3df movementPlaneStart(m_centroid, m_centroid + m_axis1, m_centroid + m_axis2);
		line3df mouseRayStart = g->sceneManager->getSceneCollisionManager()->getRayFromScreenCoordinates(m_mouseStartPos, m_camera);
		movementPlaneStart.getIntersectionWithLine(mouseRayStart.start, mouseRayStart.getVector(), mouseStart);

		plane3df movementPlane(mouseStart, mouseStart + m_axis1, mouseStart + m_axis2);
		line3df mouseRay = g->sceneManager->getSceneCollisionManager()->getRayFromScreenCoordinates(mouse, m_camera);

		vector3df moveToPoint;
		if (movementPlane.getIntersectionWithLine(mouseRay.start, mouseRay.getVector(), moveToPoint))
		{
			for (u32 i = 0; i < m_nodes.size(); ++i)
			{
				vector3df move = moveToPoint - mouseStart;
				matrix4 matAbsTransform = m_nodes[i]->getParent()->getAbsoluteTransformation();
				matAbsTransform.rotateVect(move);
				m_nodes[i]->setPosition(m_nodesStartPos[i] + move);
			}
		}

		ui->updateProperties();
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CPlaneControl::draw ()
{
	SColor color = (m_highlighted || m_activated) ? m_highlightColor : m_normalColor;
	if (m_activated)
	{
		vector2df start, moveTo, moveToAxis1, moveToAxis2;
		vector3df delta = m_nodes[0]->getPosition() - m_nodesStartPos[0];

		matrix4 matAbsTransform = m_nodes[0]->getParent()->getAbsoluteTransformation();
		matAbsTransform.rotateVect(delta);

		project3DTo2D(m_centroid, &start, m_camera);
		project3DTo2D(m_centroid + delta, &moveTo, m_camera);
		project3DTo2D(m_centroid + delta.dotProduct(m_axis1) / m_axis1.dotProduct(m_axis1) * m_axis1, &moveToAxis1, m_camera);
		project3DTo2D(m_centroid + delta.dotProduct(m_axis2) / m_axis2.dotProduct(m_axis2) * m_axis2, &moveToAxis2, m_camera);

		drawThick2DLine(start, moveTo, 2, color);
		drawThick2DLine(start, moveToAxis1, 1, color);
		drawThick2DLine(start, moveToAxis2, 1, color);
	}
	else
	{
		array<vector2df> coords;
		getScreenCoordinates(&coords);
		if (coords.size() == 4)
		{
			drawThick2DLine(coords[0], coords[1], 2, color);
			drawThick2DLine(coords[1], coords[2], 2, color);
			drawThick2DLine(coords[2], coords[3], 2, color);
			drawThick2DLine(coords[3], coords[0], 2, color);
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CPlaneControl::getScreenCoordinates (array<vector2df>* coords) const
{
	vector3df nodePos = m_centroid;
	ICameraSceneNode* cam = g->sceneManager->getActiveCamera();
	plane3df pl = cam->getViewFrustum()->planes[SViewFrustum::VF_NEAR_PLANE];
	float dist = -pl.getDistanceTo(nodePos);

	float sign1 = (plane3df(nodePos, m_axis1).getDistanceTo(cam->getPosition()) >= 0) ? 1.0f : -1.0f;
	float sign2 = (plane3df(nodePos, m_axis2).getDistanceTo(cam->getPosition()) >= 0) ? 1.0f : -1.0f;

	float scale = 1.0f;
	if (dist > 1.0f)
	{
		scale = dist/10.0f;
	}

	vector3df p1 = nodePos;
	vector3df p2 = p1 + sign1*scale*m_axis1;
	vector3df p3 = p1 + sign1*scale*m_axis1 + sign2*scale*m_axis2;
	vector3df p4 = p1 + sign2*scale*m_axis2;

	vector2df proj;
	if (project3DTo2D(p1, &proj, m_camera)) coords->push_back(proj);
	if (project3DTo2D(p2, &proj, m_camera)) coords->push_back(proj);
	if (project3DTo2D(p3, &proj, m_camera)) coords->push_back(proj);
	if (project3DTo2D(p4, &proj, m_camera)) coords->push_back(proj);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
